// SPDX-License-Identifier: GPL-2.0
/*
 * GPU: VRAM on BAR
 *
 * (C) 2024.03.14 BuddyZhang1 <buddy.zhang@aliyun.com>
 */
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/pci.h>

#define DEV_NAME		"BiscuitOS-GPU-VRAM-BAR"
#define MMIO_BAR		0x00

struct BiscuitOS_pci_device {
	struct pci_dev *pdev;
	/* MMIO BAR */
	void __iomem *mmio;
	/* VRAM */
	phys_addr_t mmio_base;
	phys_addr_t mmio_end;
	phys_addr_t mmio_len;
};
static struct BiscuitOS_pci_device *bpdev;

static int BiscuitOS_pci_probe(struct pci_dev *pdev, 
				const struct pci_device_id *id)
{
	int r;

	bpdev = kzalloc(sizeof(*bpdev), GFP_KERNEL);
	if (!bpdev) {
		r = -ENOMEM;
		printk("%s ERROR: BiscuitOS PCI allocate failed.\n", DEV_NAME);
		goto err_alloc;
	}
	bpdev->pdev = pdev;

	/* ENABLE PCI DEVICE */
	r = pci_enable_device(pdev);
	if (r < 0) {
		printk("%s ERROR: PCI Device Enable failed.\n", DEV_NAME);
		goto err_enable_pci;
	}

	/* REMAPPING MMIO INTO KERNEL SPAGE */
	bpdev->mmio = pci_iomap(pdev, MMIO_BAR, pci_resource_len(pdev, MMIO_BAR));
	if (!bpdev->mmio) {
		r = -EBUSY;
		printk("%s ERROR: Remapping MMIO Failed\n", DEV_NAME);
		goto err_iomap;
	}

	/* SET MASTER */
	pci_set_master(pdev);

	/* VRAM */
	bpdev->mmio_base = pci_resource_start(pdev, MMIO_BAR);
	bpdev->mmio_end  = pci_resource_end(pdev, MMIO_BAR); 
	bpdev->mmio_len  = pci_resource_len(pdev, MMIO_BAR);

	printk("%s Success Register PCIe Device.\n", DEV_NAME);
	printk("BAR:\n");
	printk(" MMIO: %#llx - %#llx\n", bpdev->mmio_base, bpdev->mmio_end);
	printk(" RMAP: %#lx - %#lx\n", (unsigned long)bpdev->mmio,
		(unsigned long)bpdev->mmio + (unsigned long)bpdev->mmio_len);

	/* WRITE TO VRAM */
	*(unsigned long *)bpdev->mmio = 0x123456789;
	/* READ FROM VRAM */
	printk("DATA %#lx\n", *(unsigned long *)bpdev->mmio);

	return 0;

err_iomap:
	pci_disable_device(pdev);
err_enable_pci:
	kfree(bpdev);
	bpdev = NULL;
err_alloc:
	return r;
}

static void BiscuitOS_pci_remove(struct pci_dev *pdev)
{
	pci_iounmap(pdev, bpdev->mmio);
	pci_release_region(pdev, MMIO_BAR);
	pci_disable_device(pdev);
	kfree(bpdev);
	bpdev = NULL;
}

static const struct pci_device_id BiscuitOS_pci_ids[] = {
	{ PCI_DEVICE(0x1010, 0x1991), },
};

static struct pci_driver BiscuitOS_PCIe_driver = {
	.name		= DEV_NAME,
	.id_table	= BiscuitOS_pci_ids,
	.probe		= BiscuitOS_pci_probe,
	.remove		= BiscuitOS_pci_remove,
};

static int __init BiscuitOS_init(void)
{
	return pci_register_driver(&BiscuitOS_PCIe_driver);
}

static void __exit BiscuitOS_exit(void)
{
	pci_unregister_driver(&BiscuitOS_PCIe_driver);
}

module_init(BiscuitOS_init);
module_exit(BiscuitOS_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("BiscuitOS <buddy.zhang@aliyun.com>");
MODULE_DESCRIPTION("GPU");
